  '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
  ' renum43.bas
  ' Based on renumber.bas by Geoff Graham 2010  http://geoffg.net
  ' Updated by James Deakins 2013 to use newer functions of MMBasic V4.3a
  ' 
  ' ---------------------------------------------------------------------
  ' This program will renumber a basic program including references
  ' to line numbers in statements.  It is written for MMBasic, a version
  ' of GW-Basic with enhancements to support modern programming and 
  ' external devices.
  '
  ' For comparison, Geoff's original program was 218 lines.  Including
  ' the chained programs, this is around 1900 lines.
  '
  ' On the CMM, it has to be run in Mode 1 to get enough available
  ' memory. Chaining code improves memory use.
  '
  ' This program is called by RENUMBER.BAS and cannot run by itself.
  '
  
  if UCASE$(MasterFile$) <> "RENUMBER.BAS" then
    PRINT "This program must be called by RENUMBER.BAS.  Please run RENUMBER.BAS instead."
    END
  endif
  
  defaultStart=1
  defaultFirst=10
  defaultIncrement=10
  ' defaultbIncrementOnBlank=False
  ' defaultbInsertLineNumWhenBlank=False
  
  '
  DATA "THEN","GOTO","GOSUB","ELSE","SETPIN","SETTICK" 
  KeyWords = 6  ' update this if you change the list of keywords.
  DIM KeyWord$(KeyWords)
  For n = 1 to KeyWords
    Read kw$
    KeyWord$(n) = kw$
  Next n

  if bDebugToggle then
    bDebug = NOT bDebug
    PRINT: PRINT "Debug mode changed to ";
    if bDebug then
      PRINT "ON."
    else
      PRINT "OFF."
    endif
    PRINT: PRINT
  endif
  
  if bDebug then
    open "Debug.txt" for Output as #9
  endif
 
  PRINT "This program will renumber a BASIC program including references"
  PRINT "to line numbers in GOSUB, GOTO, etc.  The error checking is"
  PRINT "far from perfect, so the program must run correctly before"
  PRINT "renumbering."
  PRINT
  
  GetRequirements             ' prompt user for requirements - input & output files and how to renumber
  
  LinesInFile = CountLines(iname$) 
  DIM lines(LinesInFile, 3)   ' first entry is the line number extracted from each line, second is new line number, 
  '                           ' third is flag to show whether it is the target of a keyword.
  DIM DQuotes(LinesInFile)    ' holds the number of double quotes on each line
  DIM Remarks(LinesinFile, 2) ' first entry is posn of first single quote on each line, second is posn of first REM
  
  OPEN iname$ FOR  input AS #1 
  OPEN oname$ FOR output AS #2
  
  PerformPass1               ' error checking and array setup
  
  'Pass 2 of the file - renumber lines and save to output file
  ' ----------------------------------------------------------
  PRINT : PRINT "Writing to Output File: "
  OPEN iname$ FOR INPUT AS #1  
  FOR lineindex = 1 TO LinesInFile  
    LINE INPUT #1, inp$ 
    if bDebug Then ? #9: ? #9, "Original line is             >>>";inp$;"<<<"
    ProcessedLine$ = ProcessLine$(inp$, lineindex)
    PRINT #2, ProcessedLine$    ' send to Output file 
    PRINT ".";  
  NEXT lineindex   
  '  
  CLOSE #1, #2
  if bDebug then CLOSE #9
  PRINT  
  PRINT "Finished.  Processed";LinesInFile;" lines"  
  ERASE lines, DQuotes, Remarks, ParamPtr, Param$, Keyword$ ' free up memory    
  UpTo = UpTo + 1      ' for the Chain command
  CHAIN MasterFile$
  END    
 
  
  '  
  ''''''''''''''''''''''''''''''''''''''''''''  
  'SUBROUTINES and FUNCTIONS 
  ''''''''''''''''''''''''''''''''''''''''''''  
  '  
  
  
  '
  ''''''''''''''''''''''''''''''''''''''''''''''''''''''''
  ' SUB PerformPass1.
  ' Perform the first pass through the file. 
  ' Store the line numbers as integers in 
  ' the lines array.
  '-Warn the user if any lines are out of order 
  ' (sometimes not obvious, and a cause of weird errors!)
  '-Warn if any old style remarks signified 
  ' by REM are used.
  '''''''''''''''''''''''''''''''''''''''''''''''''''''''' 
  '
SUB PerformPass1
  '
  Local LineNum, inp$, ThisNum, bStarted, PrevNum, bLinesInOrder, bRemStatements, NextNewLineNum, DQCount, UCLine$
  Local SQPosn, bSQFound, REMPosn, In$
  
  
  PRINT : PRINT "Checking for some potential problems.... "
  
  PrevNum        = 0
  bStarted       = False
  bLinesInOrder  = True
  bREMstatements = False
  MaxOldLineNum  = 0
  MaxNewLineNum  = 0
  NextNewLineNum = first                      ' start output numbering from this value
  
  FOR LineNum = 1 to LinesInFile 
    Line INPUT #1, inp$
    ' store the line number
    ThisNum = VAL(inp$)
    if ThisNum >= start then                  ' Watch out. ThisNum = 0 for labels or lines with no line number
      bStarted = True                         ' we're at or past the input line where we start renumbering.  Never goes back to False.
    endif
    lines(LineNum, 1) = ThisNum
    if bStarted then         
      if (ThisNum > 0) OR bIncrementOnBlank OR bInsertLineNumWhenBlank then  ' ThisNum will be zero if its for a line with a label, or no linenum
        lines(LineNum, 2) = NextNewLineNum
        MaxNewLineNum = NextNewLineNum            ' we need lengths of max old and new when we add the new linenumber to a line
        If ThisNum > 0 Then MaxOldLineNum = ThisNum
        NextNewLineNum = NextNewLineNum + increment
      endif                                       ' no need for an Else statement to store zero for labels - already initialised to zero.
    else                                          ' have not got to start value in the input file yet
      lines(LineNum, 2) = ThisNum                 ' keep the old linenumber
      if ThisNum > 0 then 
        MaxOldLineNum = ThisNum
        MaxNewLineNum = ThisNum
      endif
    endif

    ' Check for lines out of order
    If (ThisNum <= PrevNum) AND (ThisNum <> 0) Then ' a blank line number (allowable) will return zero
      PRINT "File line"; LineNum; " has its program linenumber ("; ThisNum; " ) out of order!"
      bLinesInOrder = False
    Endif
    ' Check how many double quotes (may hold REM, single quote or colon which are all searched for further down in program)
    DQCount = 0
    DQStart = INSTR(1, inp$, chr$(34)) ' search for the next double quote    
    DO While DQStart > 0)
      DQCount = DQCount + 1
      DQStart = INSTR(DQStart + 1, inp$, chr$(34))
    LOOP
    DQuotes(LineNum) = DQCount
    
    ' Check for REM  
    UCLine$ = UCASE$(inp$)
    
    ' check for a single quote, because we only care about REM if it's BEFORE a single quote
    SQPosn = INSTR(1, UCLine$, "'")
    bSQFound = False
    DO While (SQPosn > 0) and (NOT bSQFound)
      if NOT InDoubleQuotes(SQPosn, UCLine$) then ' make sure it isn't in double quotes, like "Henry's"
        bSQFound = True
      else
        SQPosn = INSTR(SQPosn + 1, UCLine$, "'")
      endif
    Loop 
    Remarks(LineNum,1) = SQPosn
    if SQPosn = 0 THEN
      SQPosn = LEN(UCLine$) ' if there isn't a single quote, assume one at the far right of the line 
    endif
    
    RemPosn = GetNextREM(1, UCLine$)
    DO While (RemPosn > 0) AND (NOT bREMstatements) AND (RemPosn < SQPosn) ' only search up to the single quote
      if DQCount > 0 then
        if NOT InDoubleQuotes(RemPosn, UCLine$) then
          bREMstatements = True
        endif
      else ' no double quotes in line
        bREMstatements = True 
      endif
      if NOT bREMstatements then
        RemPosn = GetNextREM(RemPosn + 1, UCLine$)
      endif
    LOOP   
    Remarks(LineNum, 2) = RemPosn  
    if Thisnum > 0 AND bLinesInOrder then ' don't bother updating if it was a label, or if lines out of order
      PrevNum = ThisNum
    endif
  Next LineNum
  CLOSE #1
  '
  '
  ' Note: if no line numbers in the program at all MaxNewLineNum will be zero
  if MaxNewLineNum = 0 then 
    MaxNewLineNumLen = 0
  else
    MaxNewLineNumLen = LEN(Str$(MaxNewLineNum))
  endif
  '
  ' Note: if no line numbers in the program at all MaxOldLineNum will be zero
  if MaxOldLineNum = 0 then 
    MaxOldLineNumLen = 0
  else
    MaxOldLineNumLen = LEN(Str$(MaxOldLineNum))
  endif   
  '
  'Which is longer - old or new?
  if MaxOldLineNumLen > MaxNewLineNumLen then
    MaxLineNumLen = MaxOldLineNumLen
  else
    MaxLineNumLen = MaxNewLineNumLen
  endif
  
  if bDebug then    
    FOR LineNum = 1 to LinesInFile ' display the array values
      ? #9, "LineNum is"; LineNum; " Prog Line Num is "; lines(Linenum, 1); " New Line Num is "; lines(Linenum, 2)
    next Linenum
    if bIncrementOnBlank then
      ? #9, "Increment on Blank is  True. "
    else
      ? #9, "Increment on Blank is False. "
    endif
    ? #9, "MaxOLDLineNum is";MaxOldLineNum; " and its length is";MaxOldLineNumLen
    ? #9, "MaxNEWLineNum is";MaxNewLineNum; " and its length is";MaxNewLineNumLen
    ? #9, "                       MaxLineNumLen is";MaxLineNumLen
  endif
  
  if NOT bLinesInOrder Then
    ?
    ? "Duplicate line numbers were found, or lines are out of order."
    ? "Only the first duplicate or out-of-order line number of a set is"
    ? "identified, so also check the line numbers below the ones identified."
    ?
    ? "You should only continue with the renumbering process if you are"
    ? "really sure of what you are doing!"
    ?
    Input "Line Number errors found.  Continue with Renumbering (Y/N)"; In$
    if Ucase$(Left$(In$,1)) = "N" then END
  endif
  '
  if bREMstatements Then
    ?
    ? "WARNING: Old style REM statements were found (REM is the "
    ? "older way to signifying remarks).  This program identifies "
    ? "REM statements by searching for 'REM ', so be careful using "
    ? "variable names ending in rem, like Harem, Spirem, Theorem "
    ? "or Millirem in your program. "
    ? "Although the program has been written to account for the "
    ? "various ways the REM string might appear in your program, "
    ? "it is not guaranteed to work in all cases. "
    ? "If possible, change the variable names to not end in rem. "      
    ? "If you decide to continue with the renumbering process "
    ? "without changing the variable names, check the result carefully. "
    ?
    INPUT "Do you want to continue (Y/N)"; In$
    if Ucase$(Left$(In$,1)) = "N" Then END
  endif     
  
END SUB ' PerformPass1
  '
  ''''''''''''''''''''''''''''''''''''''''''''  
  'Function  
  'process a line and replace all line numbers.
  'Parameters: pInp$ - the line to be modified
  '            pLineIndex - the line counter from
  '                         the input file
  'Returns the modified line  
  ''''''''''''''''''''''''''''''''''''''''''''
Function ProcessLine$(pInp$, pLineIndex)  
  '
  bLineNumLabel = FALSE
  ptr = 1
  
  if lines(pLineIndex,1) > 0 then ' there's a line number with possible leading spaces
    ptr = MovePtrPastSpaces(pInp$, 1)
  endif
  
  ' move pointer past line number
  ptr = MovePtrPastLineNumber(pInp$, ptr)
  ' pointer is now pointing to the first character after the line number - normally a space.
  
  ' unless its a rare occurence - a colon directly after the line number - in which case it's a label too.
  if mid$(pInp$,ptr,1) = ":" then
    bLineNumLabel = TRUE
    ptr = ptr + 1
    if bDebug then ? #9, "Line Number"; pLineIndex; " has a LineNumber label. Ptr is now"; ptr
  endif
  '
  'insert the new line number.  
  '---------------------------
  NewLine$ = AddNewLineNumber$(pInp$, ptr, pLineIndex, bLineNumLabel)
  
  UCnewline$ = UCASE$(NewLine$)
  ' A line can be made up of code and remarks.  Don't search for keywords in remarks.
  ' Separate the remarks from the code, and just process the code from now on.
  ' We will reattach the remarks after the code is processed.
  
  ' In pass one we stored pointers to the first single quote and REM in the line that weren't in double quotes
  
  ' look for the start of remarks. Could be a single quote (') or REM.  If both, use the first one on the line.
  
  ptrSingleQuote = Remarks(pLineIndex, 1)
  ptrREM         = Remarks(pLineIndex, 2) 
  
  ptrRemStart = ptrSingleQuote  'ptrRemStart will hold the earliest Start Of Remarks address, whether ' or REM
  
  if (ptrREM > 0) then
    if ptrSingleQuote = 0 then 
      ptrRemStart = ptrREM
    elseif (ptrREM < ptrSingleQuote) Then
      ptrRemStart = ptrREM
    endif
  endif
  
  ' Split the line into code and comments
  
  If ptrRemStart > 0 Then
    LenDiff = LEN(newline$) - LEN(pInp$) ' we've probably added spacing to the beginning of the line
    ptrRemStart = ptrRemStart + LenDiff
    Remarks$ = MID$(newline$, ptrRemStart)
    Code$ = Left$(newline$, ptrRemStart - 1)
  else  'it is all code 
    Remarks$ = ""
    Code$ = newline$ 
  endif
  
  if bDebug Then
    ? #9, "ptrSQ is  >>>";ptrSingleQuote;"<<<"
    ? #9, "ptrREM is >>>";ptrREM;"<<<"
    ? #9, "LenDiff   >>>";LenDiff;"<<<"
    ? #9, "Rem Start >>>";ptrRemStart;"<<<"
    ? #9, "Code is   >>>";Code$;"<<<"
    ? #9, "Rem  is   >>>";Remarks$;"<<<"
  endif
  
  ' A code line may be made up of multiple statements separated by colons (:)
  ' Process each statement separately, and reassemble.
  
  startOfStatement = 1
  ProcessedLine$ = ""
  bValidColon = False
  
  if bDebug then
    ? #9, "ProcessLine: Searching for valid colons in code line: ";Code$
  endif
  
  DO 
    nextColon = INSTR(startOfStatement, Code$, ":")
    if bDebug then ? #9, "NextColon is"; nextColon; " and bValidColon is"; bValidColon
    DO WHILE NOT (bValidColon) AND (nextColon > 0)       
      if NOT InDoubleQuotes(nextColon, Code$) then ' only OK if it's not in double quotes
        bValidColon = True
      else
        nextColon = INSTR(nextColon + 1, Code$, ":")
      endif
    LOOP
    
    if nextColon = 0 then 
      endOfStatement = len(Code$)
    else
      endOfStatement = nextColon
    endif
    ' extract next statement and process it
    Statement$ = Mid$(Code$, startOfStatement, endOfStatement - startOfStatement + 1)
    If bDebug Then ? #9, "Processing Statement >>>";Statement$;"<<<"
    ProcessedLine$ = ProcessedLine$ + ProcessStatement$(Statement$)
    if bDebug Then ? #9, "New Line is now              >>>";ProcessedLine$;"<<<"
    startOfStatement = nextColon + 1 
  LOOP Until nextColon = 0
  
  ProcessedLine$ = ProcessedLine$ + Remarks$   ' reattach remarks
  if bDebug Then ? #9, "And with Remarks reattached  >>>";ProcessedLine$;"<<<"
  ProcessLine$ = ProcessedLine$
  
End Function ' ProcessLine$
  '
  '''''''''''''''''''''''''''''''''''''''''''''''
  ' Function
  ' Process the statement
  ' Return processed statement
  '''''''''''''''''''''''''''''''''''''''''''''''
  ' Note: the original code by Geoff had separate
  ' processing for the ON command. The ON processing
  ' has now been rolled into the same code as GOTO
  ' and GOSUB.
  ' GOTO and GOSUB will handle both a single line
  ' number or label, or multiple ones separated by 
  ' commas.
  '''''''''''''''''''''''''''''''''''''''''''''''
  '
Function ProcessStatement$(pStatement$)
  ' NOTE: The newStatement$ will be a different length to pStatement$ if the new 
  '       line numbers embedded in it are different lengths 
  
  Local i, newStatement$, UCstatement$, kwPtr ' points to posn of keyword in statement
  
  bProcessedKeyword = false
  newStatement$ = pStatement$
  
  for i = 1 to KeyWords
    kw$ = KeyWord$(i)
    kwPtr = 1
    DO WHILE kwPtr <> 0
      kwPtr = INSTR(kwPtr, Ucase$(newStatement$), kw$)     ' find the keyword
      if bDebug then ? #9, "Search for keyword ";kw$; space$(7-len(kw$)); " returned kwPtr";kwptr; " (0 means not found)"
      IF kwPtr <> 0 THEN  
        IF NOT InDoubleQuotes(kwPtr, newStatement$) Then
          kwPtr = kwPtr + LEN(kw$)              'step over it.  pointer at char after end of keyword.
          newStatement$ = ProcessKeyWord$(kwPtr, newStatement$, kw$)
          bProcessedKeyword = True'
          if bDebug then
             ? #9, "kwPtr returned was ";kwPtr
          endif
        ELSE 'it is in double quotes, so do not process it as a keyword
          kwPtr = kwPtr + LEN(kw$)              'step over it
        ENDIF
      ENDIF  
    LOOP 
  Next i  
    
  If bProcessedKeyword = False then ' no keywords found
    newStatement$ = pStatement$     'Leave as is
  endif
    
  ProcessStatement$ = newStatement$
End Function  ' ProcessStatement$      
    '
    '''''''''''''''''''''''''''''''''''''''''''''
    ' Function
    ' Function is passed a pointer to where the
    ' nominated keyword is in the nominated statement.
    ' Return processed statement and revised pointer.
    '
    '''''''''''''''''''''''''''''''''''''''''''''
    ' NOTE: old form of IF allowed linenumber directly 
    ' after THEN and ELSE, but had to all be on one line.
    ' Otherwise the GOTO linenumber and GOSUB linenumber 
    ' processing would handle the IF THEN ELSE statement.
    '''''''''''''''''''''''''''''''''''''''''''''''''''''
    '
Function ProcessKeyWord$(pPtr, pStatement$, pKw$) 
    
    Local ptr, updatedPtr
    
    updatedPtr = pPtr ' return value
    
    ' move past space/s
    ptr = MovePtrPastSpaces(pStatement$, pPtr)
    
    params = parse(pStatement$, ptr)
    
  if pKw$ = "THEN" OR pKw$ = "ELSE" then ' for obsolete form - Then or Else immediately followed by line num or label
    
    if bDebug then ? #9, "Entering THEN or ELSE processing with keyword ";pKw$; " and param ";param$(1)
    
    P1$ = param$(1)
    endP1Ptr = paramPtr(1) + LEN(param$(1))
    if params = 0 then ' it's continued on the next line, so it can't be the obsolete form
      updatedStatement$ = pStatement$ ' so leave it as it is and the GOTO or GOSUB processing will handle it
      updatedPtr = LEN(Statement$)
    elseif ( UCASE$(P1$) = "GOTO" ) OR ( UCASE$(P1$) = "GOSUB" ) then
      ' "Next term is GOTO or GOSUB, so leaving pointer and statement as is"
      updatedStatement$ = pStatement$ ' gosub and goto processing will handle it
    elseif VAL(P1$) = 0 Then ' a label
      ' Next term is a label, so leaving it as is
      updatedStatement$ = pStatement$
      updatedPtr = endP1Ptr
    else
      ' "Updating obsolete form of Then or Else where the line numbr immediately follows the keyword (ie, no GOTO)"
      newNumber$ = LookupLine$(P1$)
      updatedStatement$ = Left$(pStatement$, paramPtr(1) - 1) + newNumber$ + MID$(pStatement$, endP1Ptr)
      updatedPtr = paramPtr(1) + LEN(newNumber$)
      if bDebug then 
        ? #9, "Updated Statement is >>>";updatedStatement$;"<<<"
        ? #9, "Balance of string is >>>";mid$(updatedStatement$, updatedptr);"<<<"
        ? #9, "New pointer is ";updatedPtr
      endif
    endif  
    
  elseif pKw$ = "GOTO" OR pKw$ = "GOSUB" then 
    
    if bDebug then ? #9, "Entering GOTO or GOSUB processing with keyword ";pKw$; " and param ";param$(1)
    
    ProcessMultipleTargets(pStatement$, ptr, params)
    updatedStatement$ = pStatement$
    updatedPtr = ptr ' pass back up the call chain
    
    elseif pKw$ = "SETPIN" then  ' param 3 optional
      
      if params = 2 then
        updatedStatement$ = pStatement$ ' doesn't use optional label or line number
        updatedPtr = paramPtr(2) + LEN(param$(2))
      else
        P3$ = param$(3) ' a label or line number
        endP3Ptr = paramPtr(3) + LEN(P3$)
        if VAL(P3$) = 0 then
          updatedStatement$ = pStatement$ ' a label
        else
          newNumber$ = LookupLine$(P3$)
          updatedStatement$ = Left$(pStatement$, paramPtr(3) - 1) + newNumber$ + MID$(pStatement$, endP3Ptr)
        endif
        updatedPtr = paramPtr(3) + LEN(newNumber$)
      endif 
      
    elseif pKw$ = "SETTICK" then ' param 2 mandatory
      ' do not change if target (param 2) is 0, ie settick 0,0 but otherwise change the second parameter 
      
      if params = 2 then
        P2$ = param$(2)
        endP2Ptr = paramPtr(2) + LEN(P2$)
        if P2$ = "0" then
          updatedStatement$ = pStatement$ ' reset of SetTick interrupt.  Don't change!
        elseif VAL(P2$) = 0 then
          updatedStatement$ = pStatement$ ' a label
        else
          newNumber$ = LookupLine$(P2$)
          updatedStatement$ = Left$(pStatement$, paramPtr(2) - 1) + newNumber$ + MID$(pStatement$, endP2Ptr)
        endif
        updatedPtr = paramPtr(2) + LEN(newNumber$)
      else
        ? "Malformed SETTICK command: " + pStatement$
        INPUT "Press Ctrl-C to stop, any other key to continue "; In$
      endif
    else 
      "You should never get this message.  The program tried to process the unknown keyword "; pKw$
      Input "Press Ctrl-C to break, or any other key to continue"; In$
    endif
    
    pPtr = updatedPtr
    ProcessKeyWord$ = updatedStatement$    
    
End Function  ' ProcessKeyWord$
    '
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    ' Subroutine 
    ' This subroutine processes 1 or more labels or linenumbers separated by commas 
    ' and optional spaces following a GOTO or GOSUB keyword, and it also processes 
    ' the normal single label / linenumber calls like if x = 3 then goto 10 and, through 2
    ' separate calls to this subroutine, calls like If x = 3 then goto 10 else goto 20.
    '
    ' enters the routine with the pointer pointing at the first character of the first token
    ' after the keyword, and the Param$ array holds the token values.
    ' Returns two values, updated pStatement$ and ptr.
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    '
Sub ProcessMultipleTargets(pStatement$, ptr, params)
    
    Local updatedStatement$, P$, n, newNumber$, bExit
    
    updatedStatement$ = Left$(pStatement$, ptr - 1) ' start with everything up to the start of first linenum/label
    bExit = False
    n = 1
    
    if bDebug then ? #9, "In ProcessMultipleStatements with pointer ";ptr; " and "; params ; " parameters"
    
    DO While (n <= params) AND (NOT bExit)
      P$ = param$(n) 
      PLen = LEN(P$) 
      endPPtr = paramPtr(n) + PLen - 1
      if n < params then
        separator$ = mid$(pStatement$, endPPtr + 1, paramPtr(n+1) - endPPtr - 1)
      else
        if endPPtr = LEN(pStatement$) then ' for example, if the last token is a single character like :, there's nothing following it
          separator$ = ""
        else
          separator$ = mid$(pStatement$, endPPtr + 1)
        endif
      endif
      if VAL(P$) = 0 Then  ' a label
        updatedStatement$ = updatedStatement$ + P$ + separator$
      else
        newNumber$ = LookupLine$(P$)
        updatedStatement$ = updatedStatement$ + newNumber$ + separator$   
      endif
      
      UCP$ = UCASE$(param$(n+1))
      if UCP$ = "ELSE" then 
        bExit = True
      endif
      n = n + 1
    Loop
    ptr = endPPtr
    pStatement$ = updatedStatement$   
End Sub   ' ProcessMultipleTargets
    
    '
    '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    ' Function
    ' Parse the string into tokens based on spaces and commas
    ' as separators.  Store the tokens in the global Param$ array.
    ' Store the offset within the original line of the start of 
    ' each token into another global array, ParamPtr.
    '
    ' Returns the number of parameters in the array
    '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function Parse(pStatement$, pPtr) 
    Local i, n, nextChar$, StatementLen
    
    ' Clear the array that will hold the parameters
    For i = 1 to MaxParams
      Param$(i) = ""
      ParamPtr(i) = 0
    Next i
    
    StatementLen = Len(pStatement$)
    
    ParamNum = 0  
    n = pPtr
    ' skip any leading spaces
    NextChar$ = mid$(pStatement$, n, 1)
    Do While NextChar$ = " "
      n = n + 1
      NextChar$ = mid$(pStatement$, n, 1)
    Loop
    
    ' At this point we have skipped all the spaces (if any) after the keyword and are pointing at the first parameter
    
    Do While n <= StatementLen
      Parameter$ = ""
      ParamNum = ParamNum + 1
      ParamPtr(ParamNum) = n
      
      Do While (NextChar$ <> " ") AND (NextChar$ <> ",") AND (n <= StatementLen)
        Parameter$ = Parameter$ + NextChar$
        n = n + 1
        NextChar$ = mid$(pStatement$, n, 1)
      Loop
      
      Param$(ParamNum) = Parameter$
      
      Do While ((NextChar$ = " ") OR (NextChar$ = ",")) AND (n <= StatementLen)
        n = n + 1
        NextChar$ = mid$(pStatement$, n, 1)
      Loop
    Loop
    
    if bDebug then
      for i = 1 to ParamNum
        ? #9, "Param ";i; " is "; Param$(i)
      next i
    endif
    
    Parse = ParamNum
end Function   ' Parse
    '
    '''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    ' Function
    ' Search the "lines" array to find the original line number.
    ' Return the value of the new line number.
    '''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    '
Function LookupLine$(OldLine$)
    Local bFound, i, OldLine, NewLine
    
    bFound = False
    i = 1
    OldLine = VAL(OldLine$)
    Do While (i <= LinesInFile) AND NOT bFound
      IF lines(i, 1) = OldLine THEN 
        bFound = True  
      Else
        i = i + 1
      Endif
    Loop
    
    if NOT bFound then
      PRINT : PRINT "No matching line number " + NextNumber$ + " found in original program."
      END  
    Endif
    
    ' flag this line as being the target of a keyword (like GOTO, GOGUB, SETTICK etc)
    lines(1, 3) = 1
    
    NewLine = lines(i, 2)    ' the numeric value of the new linenumber, almost ready for return
    if bDebug then
      LookupLine$ = "***" + STR$(NewLine) 'the *** is to ensure original and new are different lengths, and identify unprocessed linenums
    else
      LookupLine$ = STR$(NewLine)
    endif 
    
End Function   ' LookupLine$
    '
    '''''''''''''''''''''''''''''''''''''''''''''''''''''''
    'Function
    'Return the number of lines in the input file.
    '''''''''''''''''''''''''''''''''''''''''''''''''''''''
    'Note: Closes and reopens file so input pointer reset to start.
    '''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function CountLines(FileName$)
    local Count
    '
    OPEN FileName$ for INPUT as #3
    Count = 0
    DO WHILE NOT EOF(#3)
      Line Input #3, inp$
      Count = Count + 1
    LOOP
    Close #3
    
    CountLines = Count
End Function   ' CountLines
    '
    '''''''''''''''''''''''''''''''''''''''''''''''''''''
    'Function
    'scan the line and move the pointer to the first 
    'character past any leading spaces
    '
    '''''''''''''''''''''''''''''''''''''''''''''''''''''
    ' 
Function MovePtrPastSpaces(pStr$, pPtr)
    local ptr
    ptr = pPtr
    DO WHILE mid$(pStr$,ptr,1) = " "
      ptr = ptr + 1
    LOOP
    MovePtrPastSpaces = ptr
End Function   ' MovePtrPastSpaces
    '
    '''''''''''''''''''''''''''''''''''''''''''''''''''''
    'Function
    'scan the line and move the pointer to the first 
    'character past any leading numbers
    'note: function also updates LineNum parameter
    '''''''''''''''''''''''''''''''''''''''''''''''''''''
    ' 
Function MovePtrPastLineNumber(pStr$, pPtr)
    local ptr, nbrStr$, LineNum
    ptr=pPtr
    ' then get the number (if there)  
    nbrStr$ = ""  
    ch$ = MID$(pStr$,ptr,1)
    DO WHILE ch$ >= "0" AND ch$ <= "9"  
      ptr = ptr + 1
      nbrStr$ = nbrStr$ + ch$
      ch$ = MID$(pStr$,ptr,1)
    LOOP 
    LineNum = Val(nbrStr$) 
    MovePtrPastLineNumber = ptr
End Function   ' MovePtrPastLineNumber
    '
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    ' Function
    ' Check whether the pointer is within a pair of double
    ' quotes.
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    ' Note: needed to ensure we do not incorrectly see a colon,
    ' REM or single quote as being meaningful to the code
    ' when they are actually just part of an instruction
    ' such as THEN Y$ = "This is a colon:, a quote ', or REM "
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    '
Function InDoubleQuotes(Posn, UCLine$)
    Local bInQuotes, StartPosn, EndPosn
    
    bInQuotes = False
    StartPosn = INSTR(1, UCLine$, chr$(34))
    EndPosn = INSTR(StartPosn + 1, UCLine$, chr$(34))
    
    DO While (StartPosn > 0) AND (EndPosn > 0) AND (bInQuotes = False)
      if (Posn > StartPosn) AND (Posn < EndPosn) Then
        bInQuotes = True
      else
        StartPosn = INSTR(EndPosn + 1, UCLine$, chr$(34))
        EndPosn = INSTR(StartPosn + 1, UCLine$, chr$(34))  
      endif
    Loop
    
    InDoubleQuotes = bInQuotes
End Function   ' InDoubleQuotes
    
    '
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    ' Function
    ' Get the next REM on the line
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    ' REM can occur at the beginning of a line with nothing before it.
    ' REM can occur immediately after a colon.
    ' Other than that, it must be preceded by a space.
    '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    '
Function GetNextREM(Posn, UCLine$)
    Local i, bFound, REMPosn
    
    REMPosn = 0
    i = INSTR(Posn, UCLine$, "REM ")
    
    DO While (REMPosn = 0) AND (i > 0)
      if i = 1 then 'start of line 'REM '
        REMPosn = i
      else
        if MID$(UCLine$, i-1, 1) = " " then ' REM '
          REMPosn = i
        else
          if MID$(UCLine$,i-1, 1) = ":" then ':REM '
            REMPosn = i
          endif
        endif
      endif
      i = INSTR(i + 1, UCLine$, "REM ")
    LOOP
    
    GetNextREM = REMPosn
End Function   ' GetNextREM
    
    '
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    ' Function
    ' Returns a line with the new line number replacing the old.
    ' We enter this function with ptr pointing to the first  
    ' character after the old line number.
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    ' bInsertLineNumWhenBlank must be True before we
    ' will insert a new linenumber on a line with no existing
    ' line number.  However we can increment the linenumber
    ' to account for lines with no line number, even though
    ' we do not insert the new number, if bIncrementOnBlank
    ' is True.
    ' If bIncrementOnBlank is True, Lines(x,2) will contain
    ' the new line number to use.
    ' If bIncrementOnBlank is False, Lines(x,2) will be 0
    ' if lines(x,1) is 0. 
    ' Lines(x,3) will be 1 if the line is the target of
    ' a GOTO, GOSUB etc.
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    '     
Function AddNewLineNumber$(pInp$, ptr, pLineIndex, bLineNumLabel)
    
    Local NewLineNum, NewLineNumLen, OldNumLine, OldLineNumLen, Indent, Indent1, NewLine$, firstCharPtr, restOfLinePtr
    
    OldLineNum = lines(pLineIndex, 1)
    NewLineNum = lines(pLineIndex, 2)
    '
    if bDebug then 
      ? #9, "^^ pLineIndex is"; pLineIndex; ", NewLineNum is "; NewLineNum; ", Line is >>>";pInp$;"<<<"; " Ptr is";ptr
      ? #9, "   OldLineNum is"; OldLineNum
    endif
    
    ' get the length of the old line number
    if OldLineNum = 0 then
      OldLineNumLen = 0
    else
      OldLineNumLen = LEN(STR$(OldLineNum))
    endif
    
    ' get the length of the new line number
    if NewLineNum = 0 then
      NewLineNumLen = 0
    else
      NewLineNumLen = LEN(str$(NewLineNum)) 
    endif
    
    firstCharPtr = MovePtrPastSpaces(pInp$, ptr) ' First non-space char in pInp$.  We need this to make sure we don't overwrite non-space chars
    
    Indent = MaxLineNumLen - NewLineNumLen 'so we can space all line numbers out to the length of the longest - old or new.
    
    if firstCharPtr > (MaxOldLineNumLen) then
      RestOfLinePtr = MaxOldLineNumLen + 1  'point to character past end of number
      Indent1 = 1 
    else
      'RestOfLinePtr = firstCharPtr
      RestOfLinePtr = ptr
      'Indent1 = MaxOldLineNumLen - firstCharPtr
      Indent1 = 1
    endif
    
    if bDebug then 
      ? #9, "NewLineNumLen is"; NewLineNumLen; " OldLineNumLen is";OldLineNumLen; " Indent is";Indent; " Indent1 is ";Indent1
      ? #9, "firstCharPtr is"; firstCharPtr;" MaxOldLineNumLen is";MaxOldLineNumLen;" RestOfLinePtr =";RestOfLinePtr
    endif
    '
    If OldLineNum) > 0 THEN ' it originally had a line number 
      if bLineNumLabel then ' a linenumber immediately followed by a colon
        newline$ = STR$(NewLineNum) + ":" + space$(Indent) + space$(Indent1) + MID$(pInp$, Ptr)
      else
        newline$ = STR$(NewLineNum) + space$(Indent) + space$(Indent1) + MID$(pInp$, RestOfLinePtr)
      endif
    Else ' no line number
      n = INSTR(ptr, pInp$, ":")

      if n > 0 then ' could be a label without a line number
        bLabel = True
        for i = ptr to n 
          ch$ = MID$(pInp$, i, 1)
          if (ch$ = " ") then bLabel = False ' a label is a string immediately followed by a colon, so cannot have a space
        next i

        if bLabel then
          newLine$ = mid$(pInp$, ptr) ' output with no indent (ie, at start of line)
        else   ' ? "colon in multi statement.  Not a label.

          if bInsertLineNumWhenBlank then
            newline$ = STR$(NewLineNum)      + space$(Indent) + space$(Indent1 + 1) + MID$(pInp$, RestOfLinePtr)
          else   
            newline$ = space$(NewLineNumLen) + space$(Indent) + space$(Indent1 + 1) + MID$(pInp$, RestOfLinePtr)  
          endif

        endif
      else ' ? "no colon, no line number" 
        if bInsertLineNumWhenBlank then
          if bDebug then ? #9, "bInsertLineNumWhenBlank is True and NewLineNum is";NewLineNum; " and ptr is ";ptr
          newline$ = STR$(NewLineNum) + space$(Indent) + space$(Indent1  +1) + MID$(pInp$, RestOfLinePtr) 'fudge: added 1 to Indent1 so spaced from inserted linenumber
          else
            if bDebug then ? #9, "bInsertLineNumWhenBlank is False and NewLineNum is";NewLineNum; " and ptr is ";ptr
            if bDebug then ? #9, "RestOfLinePtr is";RestOfLinePtr; " Indent is";Indent; " Indent1 is";Indent1
            newline$ = space$(NewLineNumLen) + space$(Indent) + space$(Indent1) + MID$(pInp$, RestOfLinePtr)
          endif
        endif
      Endif 
    Endif
      
    if bDebug then ? #9, "after new line num added     >>>";newline$;"<<<"
      
    AddNewLineNumber$ = NewLine$
      
End Function 'AddNewLineNumber$

'
''''''''''''''''''''''''''''''''''''''''''''
' sub GetRequirements
' Prompt user for requirements
''''''''''''''''''''''''''''''''''''''''''''
' In RENUMBER, the program that chains to this, 
' we parsed the command line.  The number of
' parameters was stored in variable CMDs, 
' and the parameters were stored in array
' CmdParamIn$.
'''''''''''''''''''''''''''''''''''''''''''
'
SUB GetRequirements
  
  Local Answer$, bOK, In$       
  
  DisplayPrevValues
  
  if bLastValues then     ' user specified 'just do it (/!) in calling program.
    Answer$ = "Y"
  else
    INPUT "Do you want to use these values (Y/N)"; Answer$
    Answer$ = UCASE$(Left$(Answer$,1))
  endif
  IF Answer$ = "Y" THEN
    iname$ = lastOpen$
    oname$ = lastOut$
    start = lastStart
    first = lastFirst
    increment = lastIncrement
    bInsertLineNumWhenBlank = lastbInsertLineNumWhenBlank
    bIncrementOnBlank = lastbIncrementOnBlank
  ELSE ' user wants to change values.
    PRINT "If you press Enter we will use the input file name you"
    PRINT "passed in, or if you didn't supply a command line parameter,"
    PRINT "we use the 'last filename to renumber' from the display above."
    PRINT
    INPUT "Program to renumber: (Enter for param you passed in, or last used) ", iname$
    If iname$ = "" then 
      if CMDs = 0 then
        iname$ = lastOpen$
      else
        iname$ = CmdParamIn$(1)
      endif
    else
      if instr(1, iname$, ".") = 0 then
        iname$ = iname$ + ".bas"
      endif
    endif
    lastOpen$ = iname$
    
    PRINT
    PRINT "If you press Enter we will use the output file name you"
    PRINT "passed in, or if you didn't supply a command line parameter,"
    PRINT "we use the 'last output file' from the display above."
    PRINT
    INPUT "   Output file name: (Enter for param you passed in, or last used) ", oname$
    If oname$ = "" then 
      if CMDs = 0 then
        oname$ = lastOut$
      else
        oname$ = CmdParamIn$(2)
      endif
    else
      if instr(1, oname$, ".") = 0 then
        oname$ = oname$ + ".bas"
      endif
    endif
    lastOut$ = oname$
    
    bOK = False
    Do While NOT bOK
      PRINT : PRINT "In the input file, what is the desired Line Number where"
      PRINT "  renumbering is to start (Enter key for default"; defaultStart; ")"; : INPUT In$
      If In$ = "" then
        start = defaultStart 
      else 
        start = VAL(In$) 
        defaultFirst = start  ' because First can't be less than Start
      endif  
      
      PRINT : PRINT "In the output file, what is the desired first line number to "
      PRINT "be used in the new sequence (Enter key for default";defaultFirst; ")"; : INPUT In$
      If In$ = "" then 
        first = defaultFirst 
      else 
        first = VAL(In$)
      endif
      
      PRINT: PRINT "and the desired increment for each line (Enter key for default"; defaultIncrement; ")"; : INPUT In$
      If In$ = "" then 
        increment = defaultIncrement 
      else 
        increment = VAL(In$)
      endif
      
      If first + ((LinesInFile - 1) * increment) > 65000 then
        PRINT
        PRINT "If those settings are used, some of the new line numbers will be"
        PRINT "greater than the maximum allowed in MMBasic, which is 65000."
        PRINT "Please choose different values."
      else
        bOK = True
      endif
      If first < start then
        PRINT 
        PRINT "The Start line number in the Input  file must be less than or equal to"
        PRINT "the First line number in the Output file!"
        PRINT "Please choose different values."
        PRINT
        bOK = False
      endif
    LOOP
    lastStart     = start
    lastFirst     = first
    lastIncrement = increment
    '
    PRINT
    PRINT "Do you want line numbers to be inserted on those lines"
    PRINT "that currently do not have line numbers (Y/N)"; : INPUT In$
    if UCase$(In$) = "Y" then
      bInsertLineNumWhenBlank = True
      bIncrementOnBlank = False         ' because there will not be any blanks left
    else
      bInsertLineNumWhenBlank = False
      PRINT
      PRINT "Do you want line numbers to increment over lines"
      PRINT "without line numbers (Y/N)"; : INPUT In$
      PRINT
      bIncrementOnBlank = (UCASE$(In$) = "Y")
    endif
    lastbInsertLineNumWhenBlank = bInsertLineNumWhenBlank
    lastbIncrementOnBlank       = bIncrementOnBlank
  ENDIF
  
END SUB ' GetRequirements
'
'''''''''''''''''''''''''''''''''''''''''''''''''''''''
' SUB DisplayPrevValues
' Reads values from the config file and displays them
'''''''''''''''''''''''''''''''''''''''''''''''''''''''
'
SUB DisplayPrevValues
' TODO Replace this with computer generated code

  if CMDs >= 2 then
    lastOpen$ = CmdParamIn$(1)
    lastOut$  = CmdParamIn$(2)
    lastFormatBasParameters$ = FormatBasParameters$
    PRINT "The previous values you used have been updated to include"
    PRINT "the parameters you passed to RENUMBER."
    PRINT
  else
    PRINT "The previous values you used were:"
  endif
  PRINT "Last file to renumber:                 "; lastOpen$
  PRINT "Last output file:                      "; lastOut$
  PRINT "First line in file to be renumbered:  "; lastStart
  PRINT "Start line in output file:            "; lastFirst
  PRINT "Increment value:                      "; lastIncrement
  PRINT "Insert Line Number when blank:         "; 
  IF lastbInsertLineNumWhenBlank THEN
    PRINT "YES"
  ELSE
    PRINT "NO"
  ENDIF
  PRINT "Increment Line Number for blank lines: "; 
  IF lastbIncrementOnBlank THEN
    PRINT "YES"
  ELSE
    PRINT "NO"
  ENDIF
  PRINT "Call Format program:                   "; 
  IF lastbCallFormat THEN
    PRINT "YES"
  ELSE
    PRINT "NO"
  ENDIF
  PRINT "Parameters for FORMAT.BAS:             "; lastFormatBasParameters$;
  PRINT
  
END SUB